use std::{
    collections::{HashMap, HashSet},
    net::IpAddr,
};

use crate::models::Error;

pub struct Storage {
    devices: HashSet<u32>,
    connection: HashMap<u32, Device>,
}

impl Default for Storage {
    fn default() -> Self {
        Storage {
            devices: HashSet::new(),
            connection: HashMap::new(),
        }
    }
}

impl Storage {
    pub fn register(&mut self, uid: u32) -> Result<(), Error> {
        if self.devices.contains(&uid) {
            return Err(Error::Exist);
        }

        self.devices.insert(uid);
        self.connection.insert(uid, Device::default());

        Ok(())
    }

    // set device bussey with uid and set owner's ip address
    pub fn occupy(&mut self, uid: u32, address: IpAddr) -> Result<(), Error> {
        if !self.devices.contains(&uid) {
            return Err(Error::NotExisted);
        }

        let device = self.connection.get_mut(&uid).ok_or(Error::NotExisted)?;

        if device.is_occupied() {
            return Err(Error::Occupied);
        }

        device.into_occupied_with(address);

        Ok(())
    }

    pub fn is_occupied(&self, uid: u32) -> bool {
        match self.connection.get(&uid) {
            Some(item) => item.is_occupied(),
            None => false,
        }
    }

    pub fn into_vacant(&mut self, uid: u32) -> Result<(), Error> {
        self.connection
            .entry(uid)
            .and_modify(|item| item.into_vacant());
        Ok(())
    }

    pub fn who(&self, uid: u32) -> Option<IpAddr> {
        match self.connection.get(&uid) {
            Some(device) => device.occupier(),
            None => None,
        }
    }

    pub fn is_existed(&self, uid: u32) -> Result<(), Error> {
        self.devices
            .contains(&uid)
            .then_some(())
            .ok_or(Error::NotExisted)
    }
}

pub struct Device {
    owned: bool,
    address: Option<IpAddr>,
}

impl Default for Device {
    fn default() -> Self {
        Device {
            owned: false,
            address: None,
        }
    }
}

impl Device {
    pub fn into_occupied_with(&mut self, address: IpAddr) {
        self.owned = true;
        self.address = Some(address)
    }

    pub fn is_occupied(&self) -> bool {
        self.owned
    }

    pub fn occupier(&self) -> Option<IpAddr> {
        self.address
    }

    pub fn into_vacant(&mut self) {
        self.address = None;
        self.owned = false;
    }
}

#[cfg(test)]
mod test {
    use std::{
        net::{IpAddr, Ipv4Addr},
        str::FromStr,
    };

    use crate::models::Error;

    use super::Storage;

    #[test]
    fn test_device_existed() {
        let mut storage = Storage::default();

        let _ = storage.register(2);
        let existed = storage.register(2);
        assert_eq!(existed, Err(Error::Exist));
    }

    #[test]
    fn test_device_own() {
        let mut storage = Storage::default();
        let _ = storage.register(2);

        let addr = IpAddr::V4(Ipv4Addr::from_str("0.0.0.1").unwrap());

        let add = storage.occupy(2, addr);
        assert_eq!(add, Ok(()));

        let busy = storage.occupy(2, addr);
        assert_eq!(busy, Err(Error::Occupied));
    }

    #[test]
    fn test_owner() {
        let mut storage = Storage::default();
        let _ = storage.register(2);

        let addr = IpAddr::V4(Ipv4Addr::from_str("0.0.0.1").unwrap());
        let add = storage.occupy(2, addr);
        assert_eq!(add, Ok(()));
        assert_eq!(Some(addr), storage.who(2));
    }

    #[test]
    fn test_register() {
        let mut storage = Storage::default();
        assert_eq!(storage.is_existed(2), Err(Error::NotExisted));
        let _ = storage.register(2);
        assert_eq!(storage.is_existed(2), Ok(()));
    }
}
